package ws

import (
	"fmt"
	"github.com/google/uuid"
	"github.com/gorilla/websocket"
	"net/http"
	"sync"
	"time"
)

var (
	// Time allowed to write a message to the peer.
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the peer.
	pongWait = 60 * time.Second

	// Send pings to peer with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10
	// Maximum message size allowed from peer.
	maxMessageSize int64 = 2048 * 1000 //单位：字节
)

func SetMessageSize(size int64) {
	maxMessageSize = size
}

// UnExceptedCloseErr 不处理的关闭错误
var UnExceptedCloseErr = []int{
	websocket.CloseGoingAway,
	websocket.CloseAbnormalClosure,

	websocket.CloseInternalServerErr,  // 网络问题
	websocket.CloseMandatoryExtension, // 命令扩展问题
	websocket.CloseMessageTooBig,      // 消息大小太大
	websocket.ClosePolicyViolation,    // 政策不允许而关闭
	websocket.CloseProtocolError,      // 协议错误而关闭
	websocket.CloseTLSHandshake,       // tls握手问题
}

var (
	newline = []byte{'\n'}
	space   = []byte{' '}
)

var upGrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

// Client is a middleman between the websocket connection and the hub.
type Client struct {
	uuid string

	hub *Hub

	// The websocket connection.
	conn *websocket.Conn

	// Buffered channel of outbound messages.
	send chan []byte

	sendMessage chan interface{}

	closeCh   chan struct{}
	closeOnce sync.Once
	closed    bool
}

func NewServerClient(conn *websocket.Conn, hub *Hub) *Client {
	client := &Client{
		uuid:        fmt.Sprint(uuid.New()),
		hub:         hub,
		conn:        conn,
		send:        make(chan []byte, 256),
		sendMessage: make(chan interface{}, 256),
		closeCh:     make(chan struct{}),
	}

	// Allow collection of memory referenced by the caller by doing all work in
	// new goroutines.
	go client.writePump()
	go client.readPump()

	return client
}

func (c *Client) ID() string {
	return c.uuid
}

func (c *Client) Conn() *websocket.Conn {
	return c.conn
}

// readPump pumps messages from the websocket connection to the hub.
//
// The application runs readPump in a per-connection goroutine. The application
// ensures that there is at most one reader on a connection by executing all
// reads from this goroutine.
func (c *Client) readPump() {
	defer func() {
		c.hub.unregister <- c
		c.Close()
	}()
	c.conn.SetReadLimit(maxMessageSize)
	c.conn.SetReadDeadline(time.Now().Add(pongWait))
	c.conn.SetPongHandler(func(string) error {
		c.conn.SetReadDeadline(time.Now().Add(pongWait))
		return nil
	})
	//c.conn.SetPingHandler(func(string) error {
	//	return nil
	//})
	for {
		select {
		case <-c.closeCh:
			return
		default:
			messageType, message, err := c.conn.ReadMessage()
			if err != nil {
				if websocket.IsUnexpectedCloseError(err, UnExceptedCloseErr...) {
					return
				}
				logger.Error("ws read err: %v", err)
				return
			}
			//logger.Info("messageType %d", messageType)
			if ok, pong := c.hub.PongHandle(messageType, message); ok {
				c.send <- pong
				break
			}

			c.hub.messageCh <- &WebSocketMessage{Client: c, Message: message}
		}

	}
}

func (c *Client) SendMessage(message interface{}) {
	defer func() {
		if err := recover(); err != nil {
			//已close情况下无法发送则忽略
			if err == fmt.Errorf("send on closed channel") && c.closed {
				return
			}
			logger.Error("send err %s", err)
		}
	}()
	c.sendMessage <- message
}

// writePump pumps messages from the hub to the websocket connection.
//
// A goroutine running writePump is started for each connection. The
// application ensures that there is at most one writer to a connection by
// executing all writes from this goroutine.
func (c *Client) writePump() {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		c.Close()
	}()
	for {
		select {
		case <-c.closeCh:
			return
		case message, ok := <-c.sendMessage:
			//json消息
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if !ok {
				// The hub closed the channel.
				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}

			if err := c.conn.WriteJSON(message); err != nil {
				logger.Debug("write message err: %s", err)
				return
			}
			//logger.Debug("send message end: %+v", message)

		case message, ok := <-c.send:
			//二进制bytes消息
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if !ok {
				// The hub closed the channel.
				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}

			w, err := c.conn.NextWriter(websocket.TextMessage)
			if err != nil {
				return
			}
			w.Write(message)

			// Add queued chat messages to the current websocket message.
			n := len(c.send)
			for i := 0; i < n; i++ {
				w.Write(newline)
				w.Write(<-c.send)
			}

			if err := w.Close(); err != nil {
				return
			}
		case <-ticker.C:
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}
}

// Close 主动断开
func (c *Client) Close() {
	c.closeOnce.Do(func() {
		c.closed = true
		close(c.closeCh)
		close(c.send)
		close(c.sendMessage)
		logger.Info("ws closed %s", c.ID())
	})
}

// serveWs handles websocket requests from the peer.
func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
	conn, err := upGrader.Upgrade(w, r, nil)
	if err != nil {
		w.Write([]byte(fmt.Sprintf("ws upgrade err: %s", err)))
		return
	}
	client := NewServerClient(conn, hub)
	hub.register <- client

}
